package com.eedi.framework.user.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.eedi.framework.user.controller.org.vo.*;
import com.eedi.framework.biz.config.BizProperties;
import com.eedi.framework.common.enums.CommonStatusEnum;
import com.eedi.framework.common.enums.UserTypeEnum;
import com.eedi.framework.common.exception.util.PreconditionsUtils;
import com.eedi.framework.common.pojo.PageResult;
import com.eedi.framework.file.core.client.FileClient;
import com.eedi.framework.file.core.enums.MimeTypeEnum;
import com.eedi.framework.file.core.utils.FileTypeUtils;
import com.eedi.framework.user.controller.org.vo.*;
import com.eedi.framework.user.convert.OrgUserAccountImportConvert;
import com.eedi.framework.user.convert.OrgUserConvert;
import com.eedi.framework.user.convert.OrgUserDeptImportConvert;
import com.eedi.framework.user.dal.dataobject.OrgUserDO;
import com.eedi.framework.user.dal.mapper.OrgUserMapper;
import com.eedi.framework.user.enums.OrgUserErrorCodeConstants;
import com.eedi.framework.user.excel.ExcelRowNoListener;
import com.eedi.org.facade.OrgDeptFacade;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.eedi.framework.common.exception.util.ServiceExceptionUtil.exception;


@Service
@Slf4j
public class OrgUserServiceImpl implements OrgUserService {
    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    OrgUserMapper orgUserMapper;

    @Resource
    BizProperties bizProperties;

    @Resource
    private FileClient fileClientPrivate;

    @Resource
    private OrgDeptFacade orgDeptFacade;

    /**
     * 批量导入用户账号的excel模板文件
     */
    private final String TEMPLATE_EXCEL = "用户账号批量导入模板.xlsx";

    @Transactional
    @Override
    public OrgUserLoginInfoResp create(OrgUserAccountCreateReq createReq) {
        //检查登录ID
        OrgUserLoginInfoResp byLoginId = orgUserMapper.selectByLoginId(createReq.getOrgUserLoginId());
        PreconditionsUtils.checkArgument(byLoginId == null, OrgUserErrorCodeConstants.ORG_USER_LOGIN_ID_EXISTS, createReq.getOrgUserLoginId());
        //检查手机号
        OrgUserLoginInfoResp byMobileAndRole = orgUserMapper.selectByMobileAndRole(createReq.getOrgUserMobile(), UserTypeEnum.MEMBER_OPS);
        PreconditionsUtils.checkArgument(byMobileAndRole == null, OrgUserErrorCodeConstants.ORG_USER_MOBILE_EXISTS, createReq.getOrgUserLoginId());
        //密码加密
        if (ObjUtil.isNull(createReq.getOrgUserPassword())) {
            createReq.setOrgUserPassword(bizProperties.getDefaultPasswd());
        }
        createReq.setOrgUserPassword(passwordEncoder.encode(createReq.getOrgUserPassword()));
        OrgUserLoginInfoResp orgUserLoginInfoResp = orgUserMapper.create(createReq, CommonStatusEnum.ENABLE);

        orgUserLoginInfoResp.setDefaultPwd(bizProperties.getDefaultPasswd());
        return orgUserLoginInfoResp;
    }

    @Override
    public OrgUserLoginInfoResp getLoginInfoByLoginId(String orgUserLoginId) {
        return selectByLoginId(orgUserLoginId);
    }

    @Override
    public boolean isPasswordMatch(String orgUserLoginId, String password) {
        String pwdByLoginId = getPwdByLoginId(orgUserLoginId);
        return passwordEncoder.matches(password, pwdByLoginId);
    }

    @Override
    public void updateUserLogin(String userId, String loginIp) {
        selectByOrgUserId(userId);
        orgUserMapper.updateLoginTime(userId, loginIp);
    }

    @Override
    public PageResult<OrgUserLoginInfoResp> page(OrgUserAccountPageReq req) {
        return orgUserMapper.page(req);
    }

    @Override
    public PageResult<OrgUserLoginInfoResp> page(OrgUserAccountPageReq req, Collection<String> orgUserIds) {
        return orgUserMapper.page(req, orgUserIds);
    }

    @Override
    public OrgUserLoginInfoResp selectByMobileAndRole(String mobile, UserTypeEnum userTypeEnum) {
        return orgUserMapper.selectByMobileAndRole(mobile, userTypeEnum);
    }


    @Override
    public OrgUserLoginInfoResp selectByLoginId(String loginId) {
        return orgUserMapper.selectByLoginId(loginId);
    }

    @Override
    public List<OrgUserLoginInfoResp> selectByLoginIds(Set<String> loginIds) {
        return orgUserMapper.selectByLoginIds(loginIds);
    }

    @Override
    public OrgUserLoginInfoResp selectByOrgUserId(String orgUserId) {
        OrgUserLoginInfoResp orgUserLoginResp = orgUserMapper.selectByOrgUserId(orgUserId);
        PreconditionsUtils.checkNotNull(orgUserLoginResp, OrgUserErrorCodeConstants.ORG_ACCOUNT_NOT_EXISTS);
        return orgUserLoginResp;
    }

    @Override
    public List<OrgUserLoginInfoResp> selectByOrgUserIds(Collection<String> orgUserIds) {
        if (CollUtil.isEmpty(orgUserIds)) {
            return List.of();
        }
        return orgUserMapper.selectByOrgUserIds(orgUserIds);
    }

    @Override
    public OrgUserLoginInfoResp selectByOrgUserIdIgnoreStatus(String orgUserAccountId) {
        return orgUserMapper.selectByAccountIdIgnoreStatus(orgUserAccountId);
    }

    @Override
    public String resetPassword(String orgUserId) {
        OrgUserLoginInfoResp orgUserLoginResp = selectByOrgUserId(orgUserId);
        PreconditionsUtils.checkArgument(null != orgUserLoginResp, OrgUserErrorCodeConstants.ORG_USER_NOT_EXISTS);
        orgUserMapper.resetPassword(orgUserId, encodePassword(bizProperties.getDefaultPasswd()));
        return bizProperties.getDefaultPasswd();
    }

    @Override
    public void resetMobile(OrgUserAccountResetMobileReq req) {
        log.info("开始执行更新手机号操作，orgUserAccountId:{}, mobile:{}", req.getOrgUserId(), req.getOrgUserMobile());
        PreconditionsUtils.checkArgument(StringUtils.isNoneBlank(req.getOrgUserId(), req.getOrgUserMobile()), OrgUserErrorCodeConstants.ORG_USER_ACCOUNT_OR_MOBILE_NULL);
        OrgUserLoginInfoResp orgUserLoginResp = selectByOrgUserId(req.getOrgUserId());
        PreconditionsUtils.checkNotNull(orgUserLoginResp, OrgUserErrorCodeConstants.ORG_USER_NOT_EXISTS);
        PreconditionsUtils.checkArgument(!req.getOrgUserMobile().equals(orgUserLoginResp.getOrgUserMobile()), OrgUserErrorCodeConstants.ORG_USER_MOBILE_NO_CHANGE);

        log.info("开始查询手机号是否已被其他同类型账号使用，orgUserAccountId:{}, mobile:{}", req.getOrgUserId(), req.getOrgUserMobile());
        OrgUserLoginInfoResp otherAccountDO = selectByMobileAndRole(req.getOrgUserMobile(), orgUserLoginResp.getOrgUserAccountType());
        PreconditionsUtils.checkArgument(otherAccountDO == null, OrgUserErrorCodeConstants.ORG_USER_MOBILE_USED, req.getOrgUserMobile());
        orgUserMapper.resetMobile(req.getOrgUserId(), req.getOrgUserMobile());
    }

    @Override
    public void updateAccountStatus(String orgUserAccountId, CommonStatusEnum status) {
        OrgUserLoginInfoResp orgUserLoginResp = selectByOrgUserIdIgnoreStatus(orgUserAccountId);
        PreconditionsUtils.checkNotNull(orgUserLoginResp, OrgUserErrorCodeConstants.ORG_ACCOUNT_NOT_EXISTS);
        orgUserMapper.updateStatusByOrgUserAccountId(orgUserAccountId, status);
    }

    @Override
    public void disableByOrgUserAccountId(String orgUserAccountId) {
        OrgUserLoginInfoResp orgUserLoginResp = selectByOrgUserIdIgnoreStatus(orgUserAccountId);
        PreconditionsUtils.checkArgument(null != orgUserLoginResp, OrgUserErrorCodeConstants.ORG_USER_NOT_EXISTS);
        if (CommonStatusEnum.DISABLE.equals(orgUserLoginResp.getOrgUserAccountStatus())) {
            throw exception(OrgUserErrorCodeConstants.ORG_USER_ACCOUNT_IS_DISABLE);
        }
        orgUserMapper.updateStatusByOrgUserAccountId(orgUserAccountId, CommonStatusEnum.DISABLE);
    }

    @Override
    public void enableByOrgUserAccountId(String orgUserAccountId) {
        OrgUserLoginInfoResp orgUserLoginResp = selectByOrgUserIdIgnoreStatus(orgUserAccountId);
        PreconditionsUtils.checkArgument(null != orgUserLoginResp, OrgUserErrorCodeConstants.ORG_USER_NOT_EXISTS);
        if (CommonStatusEnum.ENABLE.equals(orgUserLoginResp.getOrgUserAccountStatus())) {
            throw exception(OrgUserErrorCodeConstants.ORG_USER_ACCOUNT_IS_ENABLE);
        }
        orgUserMapper.updateStatusByOrgUserAccountId(orgUserAccountId, CommonStatusEnum.ENABLE);
    }

    @Override
    public String getPwdByLoginId(String orgUserLoginId) {
        return orgUserMapper.getPwdByLoginId(orgUserLoginId);
    }

    @Override
    public void editOrgUserInfo(String orgUserId, OrgUserProfileEditReq req) {
        selectByOrgUserId(orgUserId);
        if (StrUtil.isNotBlank(req.getOrgUserLoginId())) {
            OrgUserLoginInfoResp sysUserLoginInfoResp = orgUserMapper.selectByLoginId(req.getOrgUserLoginId());
            if (null != sysUserLoginInfoResp && !orgUserId.equals(sysUserLoginInfoResp.getOrgUserId())) {
                throw exception(OrgUserErrorCodeConstants.ORG_USER_LOGIN_ID_EXISTS);
            }
        }
        orgUserMapper.editUserInfo(orgUserId, req);
    }

    @Override
    public void editPwd(String sysUserId, OrgUserProfileEditPwdReq req) {

        OrgUserLoginInfoResp user = selectByOrgUserId(sysUserId);
        PreconditionsUtils.checkArgument(isPasswordMatch(user.getOrgUserLoginId(), req.getOldPwd()), OrgUserErrorCodeConstants.ORG_USER_PASSWORD_FAILED);
        orgUserMapper.resetPassword(sysUserId, encodePassword(req.getNewPwd()));
    }

    @Override
    public Long countOrgUsers() {
        return this.orgUserMapper.selectCount();
    }

    @Override
    public void updateOrgUserInfo(OrgUserUpReq req) {
        OrgUserLoginInfoResp resp = selectByOrgUserIdIgnoreStatus(req.getOrgUserId());
        PreconditionsUtils.checkArgument(null != resp, OrgUserErrorCodeConstants.ORG_USER_NOT_EXISTS);
        orgUserMapper.updateOrgUserInfo(req);
    }

    /**
     * 批量添加用户(Excel批量导入)
     */
    @Override
    public OrgUserAccountBatchCreateResp batchCreate(String fileUrl) throws Exception {
        // 1.extract user data from excel
        boolean isXlsx = FileTypeUtils.isTypeOf(fileUrl, MimeTypeEnum.XLSX);
        boolean isXls = FileTypeUtils.isTypeOf(fileUrl, MimeTypeEnum.XLS);
        PreconditionsUtils.checkArgument(isXls || isXlsx,OrgUserErrorCodeConstants.MIME_TYPE_UNMATCHED);
        byte[] content = fileClientPrivate.getContent(fileUrl);
        ByteArrayInputStream in = new ByteArrayInputStream(content);
        ExcelRowNoListener<OrgUserAccountImportReq> listener = new ExcelRowNoListener<>();
        List<OrgUserAccountImportReq> userAccounts = EasyExcel.read(in, OrgUserAccountImportReq.class, listener)
                .autoCloseStream(false)
                .doReadAllSync();
        // validate total rows of excel data
        PreconditionsUtils.checkArgument(userAccounts.size() <= 2000 && userAccounts.size() > 0, OrgUserErrorCodeConstants.IMPORT_EXCEL_ROW_LIMIT_EXCEEDED,userAccounts.size());

        // 2.validate user list data
        Set<String> loginIdValidator = new HashSet<>();
        Set<String> mobileValidator = new HashSet<>();
        List<OrgUserAccountImportFailResp> failedRows = new ArrayList<>();
        List<OrgUserAccountImportReq> filterdUserList = new ArrayList<>();

        for (OrgUserAccountImportReq account : userAccounts) {
            String userMobile = account.getOrgUserMobile();
            String loginId = account.getOrgUserLoginId();

            // filter duplicated loginId and mobile
            if (!mobileValidator.contains(userMobile) && !loginIdValidator.contains(loginId)) {
                account.setOrgUserPassword(passwordEncoder.encode(bizProperties.getDefaultPasswd()));
                filterdUserList.add(account);
            }
            if (mobileValidator.contains(userMobile)) {
                OrgUserAccountImportFailResp importFailResp = convertOrgUserAccountImportReq(account, "手机号重复");
                failedRows.add(importFailResp);
            }
            if (loginIdValidator.contains(loginId)) {
                OrgUserAccountImportFailResp importFailResp = convertOrgUserAccountImportReq(account, "用户账号重复");
                failedRows.add(importFailResp);
            }
            mobileValidator.add(account.getOrgUserMobile());
            loginIdValidator.add(account.getOrgUserLoginId());
        }

        List<String> loginAccountList = filterdUserList.stream()
                .map(OrgUserAccountImportReq::getOrgUserLoginId)
                .toList();

        List<String> userMobileList = filterdUserList.stream().map(OrgUserAccountImportReq::getOrgUserMobile).toList();

        // compare with exists DB data
        List<OrgUserLoginInfoResp> existsUserLoginInfoList = orgUserMapper.selectByLoginIdOrMobile(loginAccountList, userMobileList);

        List<OrgUserAccountImportReq> batchInsertUserAccountList = new ArrayList<>(filterdUserList.size());

        if (Objects.nonNull(existsUserLoginInfoList) && !existsUserLoginInfoList.isEmpty()) {
            Set<String> existsLoginIdSet = existsUserLoginInfoList.stream()
                    .map(OrgUserLoginInfoResp::getOrgUserLoginId).collect(Collectors.toSet());
            Set<String> existsMobileSet = existsUserLoginInfoList.stream()
                    .map(OrgUserLoginInfoResp::getOrgUserMobile).collect(Collectors.toSet());

            for (OrgUserAccountImportReq createReq : filterdUserList) {
                if (existsLoginIdSet.contains(createReq.getOrgUserLoginId())) {
                    OrgUserAccountImportFailResp importFailResp = convertOrgUserAccountImportReq(createReq, "该用户账号已被占用");
                    failedRows.add(importFailResp);
                } else if (existsMobileSet.contains(createReq.getOrgUserMobile())) {
                    OrgUserAccountImportFailResp importFailResp = convertOrgUserAccountImportReq(createReq, "该用户手机号已被占用");
                    failedRows.add(importFailResp);
                } else {
                    batchInsertUserAccountList.add(createReq);
                }
            }
        } else {
            batchInsertUserAccountList = filterdUserList;
        }
        // 3.batch insert to DB
        if (!batchInsertUserAccountList.isEmpty()) {
            List<OrgUserDO> userDOList = OrgUserConvert.INSTANCE.convertList(batchInsertUserAccountList);
            // todo: to be optimized,mybatisx did not support batch operation with current version.
            orgUserMapper.insertBatch(userDOList);
        }

        // 4.return response
        OrgUserAccountBatchCreateResp resp = OrgUserAccountBatchCreateResp.builder()
                .importFail(userAccounts.size() != batchInsertUserAccountList.size())
                .count(userAccounts.size())
                .successCount(batchInsertUserAccountList.size())
                .failCount(userAccounts.size() - batchInsertUserAccountList.size())
                .build();

        // 5. write failed rows to excel in async mode
        if (!failedRows.isEmpty()) {
            String now = DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_MS_PATTERN);
            String fileName = "用户导入异常数据-" + now + ".xlsx";
            EasyExcel.write(fileName, OrgUserAccountImportFailResp.class).sheet().doWrite(failedRows);
            FileInputStream inputStream = new FileInputStream(fileName);
            String res = fileClientPrivate.upload(inputStream.readAllBytes(), fileName, MimeTypeEnum.XLSX.getMimeType());
            resp.setFailFileName(fileName);
            resp.setFailFileUrl(res);
            inputStream.close();
        }
        return resp;
    }

    /**
     * 批量更新用户-科室关系（Excel导入配置数据）
     *
     * @param fileUrl
     * @return
     * @throws Exception
     */
    @Override
    public OrgUserDeptBatchUpdateResp batchUpdateUserDept(String fileUrl) throws Exception {
        // 1.extract user data from excel

        // validate Mime-type for input file
        boolean isXls = FileTypeUtils.isTypeOf(fileUrl, MimeTypeEnum.XLS);
        boolean isXlsx = FileTypeUtils.isTypeOf(fileUrl, MimeTypeEnum.XLSX);
        PreconditionsUtils.checkArgument(isXls || isXlsx, OrgUserErrorCodeConstants.MIME_TYPE_UNMATCHED);

        byte[] content = fileClientPrivate.getContent(fileUrl);
        ByteArrayInputStream in = new ByteArrayInputStream(content);
        ExcelRowNoListener<OrgUserDeptImportRowReq> listener = new ExcelRowNoListener<>();
        List<OrgUserDeptImportRowReq> userDeptList = EasyExcel.read(in, OrgUserDeptImportRowReq.class, listener)
                .autoCloseStream(false)
                .doReadAllSync();
        // validate total rows of excel data
        PreconditionsUtils.checkArgument(userDeptList.size() <= 2000 && userDeptList.size() > 0, OrgUserErrorCodeConstants.IMPORT_EXCEL_ROW_LIMIT_EXCEEDED, userDeptList.size());

        Set<String> loginIdSet = userDeptList.stream().map(OrgUserDeptImportRowReq::getOrgUserLoginId).collect(Collectors.toSet());

        List<OrgUserLoginInfoResp> existsUserList = orgUserMapper.selectByLoginIds(loginIdSet);
        Map<String, OrgUserLoginInfoResp> existsLoginInfoRespMap = existsUserList.stream().collect(Collectors.toMap(OrgUserLoginInfoResp::getOrgUserLoginId, Function.identity()));

        orgDeptFacade.fillInDeptId(userDeptList, OrgUserDeptImportRowReq::getOrgUserDept, OrgUserDeptImportRowReq::setOrgDeptId);

        List<OrgUserUpReq> batchUpdateList = new ArrayList<>();
        List<OrgUserDeptImportFailResp> failedRows = new ArrayList<>();
        for (OrgUserDeptImportRowReq rowReq : userDeptList) {
            String loginId = rowReq.getOrgUserLoginId();
            String orgDeptId = rowReq.getOrgDeptId();
            if (existsLoginInfoRespMap.containsKey(loginId) && StrUtil.isNotEmpty(orgDeptId)) {
                OrgUserLoginInfoResp loginInfoResp = existsLoginInfoRespMap.get(loginId);
                OrgUserUpReq orgUserUpReq = new OrgUserUpReq();
                orgUserUpReq.setOrgUserId(loginInfoResp.getOrgUserId());
                orgUserUpReq.setOrgDeptId(orgDeptId);
                batchUpdateList.add(orgUserUpReq);
            } else if (!existsLoginInfoRespMap.containsKey(loginId)) {
                OrgUserDeptImportFailResp failedRow = OrgUserDeptImportConvert.INSTANCE.convert(rowReq);
                failedRow.setFailReason("当前账号不存在");
                failedRows.add(failedRow);
            } else {
                OrgUserDeptImportFailResp failedRow = OrgUserDeptImportConvert.INSTANCE.convert(rowReq);
                failedRow.setFailReason("指定科室不存在");
                failedRows.add(failedRow);
            }
        }

        orgUserMapper.batchUpdateDept(batchUpdateList);

        OrgUserDeptBatchUpdateResp resp = OrgUserDeptBatchUpdateResp.builder()
                .count(userDeptList.size())
                .failCount(failedRows.size())
                .successCount(userDeptList.size() - failedRows.size())
                .importFail(failedRows.size() > 0)
                .build();

        if (!failedRows.isEmpty()) {
            String now = DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_MS_PATTERN);
            String fileName = "用户-科室导入异常数据-" + now + ".xlsx";
            File file = new File(fileName);
            EasyExcel.write(file, OrgUserDeptImportFailResp.class).sheet().doWrite(failedRows);
            FileInputStream inputStream = new FileInputStream(file);
            fileClientPrivate.upload(inputStream.readAllBytes(), fileName, MimeTypeEnum.XLSX.getMimeType());
            resp.setFailFileName(fileName);
            inputStream.close();
            file.delete();
        }
        return resp;
    }

    @Override
    public List<OrgUserSimpleResp> findUserSimple(OrgUserSimpleReq req) {
        if (StrUtil.isBlank(req.getOrgUserId())&&StrUtil.isBlank(req.getOrgUserNameKey())){
            return List.of();
        }
      return   orgUserMapper.findUserSimple(req.getOrgUserNameKey(),req.getOrgUserId());
    }


    /**
     * 对密码进行加密
     *
     * @param password 密码
     * @return 加密后的密码
     */
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }

    private OrgUserAccountImportFailResp convertOrgUserAccountImportReq(OrgUserAccountImportReq src, String reason) {
        return OrgUserAccountImportConvert.INSTANCE.convert(src)
                .setRowNo(src.getRowNo())
                .setFailReason(reason);
    }
}
